package com.slowfly.rule.engine.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.slowfly.rule.engine.bean.CacheBean;
import com.slowfly.rule.engine.component.RedisComponent;
import com.slowfly.rule.engine.constants.IRiskConstants;
import com.slowfly.rule.engine.entity.Decision;
import com.slowfly.rule.engine.entity.Rule;
import com.slowfly.rule.engine.entity.Scene;
import com.slowfly.rule.engine.entity.Strategy;
import com.slowfly.rule.engine.enums.NodeTypeEnum;
import com.slowfly.rule.engine.enums.OptEnum;
import com.slowfly.rule.engine.enums.StrategyStatusEnum;
import com.slowfly.rule.engine.mapper.StrategyMapper;
import com.slowfly.rule.engine.model.Flow;
import com.slowfly.rule.engine.param.StrategyParam;
import com.slowfly.rule.engine.result.PageResult;
import com.slowfly.rule.engine.result.StrategyResult;
import com.slowfly.rule.engine.service.IDecisionService;
import com.slowfly.rule.engine.service.IRuleService;
import com.slowfly.rule.engine.service.IStrategyService;
import com.slowfly.rule.engine.util.GroovyScriptUtil;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.slowfly.rule.engine.util.JsonUtils;
import com.slowfly.rule.engine.util.PageResultConvert;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author shalongfei
 * @since 2024-02-22
 */
@Slf4j
@Service
public class StrategyServiceImpl extends ServiceImpl<StrategyMapper, Strategy> implements IStrategyService {

    @Autowired
    private IRuleService ruleService;
    @Autowired
    private IDecisionService decisionService;
    @Autowired
    private RedisComponent redisComponent;

    private Map<Integer, Strategy> strategyCache;

    public void setCache() {
        Map<Integer, Strategy> strategyCache = Maps.newConcurrentMap();
        for (Strategy strategy : list()) {
            strategyCache.put(strategy.getId(), strategy);
        }
        this.strategyCache = strategyCache;
    }

    public void editCache(int type, int id) {
        if (type == OptEnum.ADD.getCode() || type == OptEnum.EDIT.getCode()) {
            Strategy strategy = getById(id);
            checkScripts(getScripts(strategy));
            strategyCache.put(id, strategy);
        } else if (type == OptEnum.DELETE.getCode()) {
            strategyCache.remove(id);
        }
        log.info("editCache {}", JsonUtils.toJson(strategyCache));
    }

    public List<StrategyResult> listStrategy() {
        return BeanUtil.copyToList(list(), StrategyResult.class);
    }

    public List<StrategyResult> listStrategyByParam(StrategyParam param) {
        LambdaQueryWrapper<Strategy> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Objects.nonNull(param.getSceneId()), Strategy::getSceneId, param.getSceneId());
        queryWrapper.eq(Objects.nonNull(param.getStatus()), Strategy::getStatus, param.getStatus());
        return BeanUtil.copyToList(list(queryWrapper), StrategyResult.class);
    }

    public List<Strategy> listStrategy(Scene scene) {
        return strategyCache.values().stream()
                .filter(strategy -> StrategyStatusEnum.isNotOffline(strategy.getStatus()))
                .filter(strategy -> Objects.equals(scene.getId(), strategy.getSceneId()))
                .sorted().collect(Collectors.toList());
    }

    public String getScripts(Strategy strategy) {
        String scripts = getScripts(strategy.getFlows(), 2);
        return String.format(IRiskConstants.SCRIPT_FORMAT, scripts);
    }

    private String getScripts(List<Flow> flows, int indents) {
        StringBuilder builder = new StringBuilder();
        if (CollectionUtils.isEmpty(flows)) {
            return builder.toString();
        }
        for (Flow flow : flows) {
            if (Objects.equals(flow.getNodeType(), NodeTypeEnum.RULE.getCode())) {
                Rule rule = ruleService.get(flow.getNodeId());
                if (Objects.nonNull(rule)) {
                    String scripts = ruleService.getScripts(rule);
                    String children = getScripts(flow.getChildren(), 1);
                    builder.append(String.format(IRiskConstants.RULE_FORMAT, scripts, getComment(rule.getName()), children));
                }
            } else if (Objects.equals(flow.getNodeType(), NodeTypeEnum.DECISION.getCode())) {
                Decision decision = decisionService.get(flow.getNodeId());
                builder.append(String.format(IRiskConstants.RETURN_FORMAT, flow.getNodeId(), getComment(decision.getName())));
                break;
            }
        }
        // 格式化代码
        StringBuilder res = new StringBuilder();
        for (String line : builder.toString().split("\n")) {
            for (int i = 0; i < indents; i++) {
                // 缩进两个空格
                res.append("  ");
            }
            res.append(line).append("\n");
        }
        return res.toString();
    }

    public Set<Integer> getFeatureIds(Strategy strategy) {
        return getFeatureIds(strategy.getFlows());
    }

    private Set<Integer> getFeatureIds(List<Flow> flows) {
        Set<Integer> featureIds = Sets.newHashSet();
        if (CollectionUtils.isEmpty(flows)) {
            return featureIds;
        }
        for (Flow flow : flows) {
            if (Objects.equals(flow.getNodeType(), NodeTypeEnum.RULE.getCode())) {
                Rule rule = ruleService.get(flow.getNodeId());
                featureIds.addAll(ruleService.getFeatureIds(rule));
                featureIds.addAll(getFeatureIds(flow.getChildren()));
            }
        }
        return featureIds;
    }

    public PageResult<StrategyResult> pageStrategy(StrategyParam param) {
        LambdaQueryWrapper<Strategy> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Objects.nonNull(param.getSceneId()), Strategy::getSceneId, param.getSceneId());
        queryWrapper.eq(Objects.nonNull(param.getStatus()), Strategy::getStatus, param.getStatus());
        queryWrapper.eq(Objects.nonNull(param.getPriority()), Strategy::getPriority, param.getPriority());
        queryWrapper.like(StringUtils.isNotBlank(param.getName()), Strategy::getName, param.getName());
        queryWrapper.orderByDesc(Strategy::getUpdateTime);
        Page<Strategy> page = page(new Page<>(param.getPageNo(), param.getPageSize()), queryWrapper);
        return PageResultConvert.convert(page, StrategyResult.class);
    }

    public boolean addStrategy(StrategyParam param) {
        Strategy strategy = new Strategy();
        BeanUtils.copyProperties(param, strategy);
        strategy.setStatus(StrategyStatusEnum.OFFLINE.getCode());
        checkScripts(getScripts(strategy));
        boolean saved = save(strategy);
        redisComponent.publish(IRiskConstants.STRATEGY_CHANEL, new CacheBean(OptEnum.ADD.getCode(), strategy.getId()));
        return saved;
    }

    public boolean editStrategy(StrategyParam param) {
        Strategy strategy = new Strategy();
        BeanUtils.copyProperties(param, strategy);
        checkScripts(getScripts(strategy));
        boolean updated = updateById(strategy);
        redisComponent.publish(IRiskConstants.STRATEGY_CHANEL, new CacheBean(OptEnum.EDIT.getCode(), param.getId()));
        return updated;
    }

    public boolean deleteStrategy(StrategyParam param) {
        if (StrategyStatusEnum.isNotOffline(param.getStatus())) {
            throw new RuntimeException("只能删除下线状态的策略!");
        }
        boolean removed = removeById(param.getId());
        redisComponent.publish(IRiskConstants.STRATEGY_CHANEL, new CacheBean(OptEnum.DELETE.getCode(), param.getId()));
        return removed;
    }

    private String getComment(String comment) {
        // 简单防代码注入
        if (Objects.nonNull(comment) && comment.contains("\n")) {
            return "";
        }
        return comment;
    }

    private void checkScripts(String scripts) {
        try {
            if (StringUtils.isNotBlank(scripts)) {
                GroovyScriptUtil.loadScript(scripts);
            }
        } catch (Exception e) {
            throw new RuntimeException("策略决策流异常!");
        }
    }

}
